#!/usr/bin/env ruby
#
#  Copyright (c) 2010 Mu Dynamics, Inc. All rights reserved.
# class for netconfig API functions
require 'rubygems'
require 'json'
require 'net/https'
require 'uri'


class Netconfig
  attr_accessor :host, :username, :password, :docroot, :element, :response, :config

  def initialize(host=ENV['MU_IP'], docroot="/restlet/netconfig/")
    @host = host
    @username  = (ENV['MU_ADMIN_USER'].nil?) ? "admin" : ENV['MU_ADMIN_USER']
    @password  = (ENV['MU_ADMIN_PASS'].nil?) ? "admin" : ENV['MU_ADMIN_PASS']
    @docroot = docroot
    @response = nil
    @element = "" # sticky variable will hold a default element, the last element specified
    @config = get "all"
    $DEBUG and puts "Created Netconfig API object to :#{@host}"
  end

  # get - with no params, returns the entire netconfig json object
  def get(e=@element)
    @element = e
    uri = URI.parse("https://#{@host}/")
    @http = Net::HTTP.new(uri.host, uri.port)
    @http.use_ssl = true
    escaped = URI.escape("#{@docroot}#{e}")
    $DEBUG and puts "Get: #{escaped}"
    @http.start do |http|
      @req = Net::HTTP::Get.new(escaped, {"User-Agent" => @username})
      http_request
    end
    begin
      jresp = JSON @response.body
      if jresp 
        return jresp
      end
    rescue JSON::ParserError
      # nothing to do
    end
  end

  # PUT to modify netconfig element(s) to json values
  def modify(json, e=@element)
    @element = e
    jstring = json
    unless json.is_a? String
      jstring = JSON.generate json
    end
    uri = URI.parse("https://#{@host}/")
    escaped = URI.escape("#{@docroot}#{e}")
    $DEBUG and puts "Put: #{escaped}"
    @http = Net::HTTP.new(uri.host, uri.port)
    @http.use_ssl = true
    @http.start do |http|
      @req = Net::HTTP::Put.new("#{escaped}",initheader = {"User-Agent" => @username, 'Content-Type' => 'application/json; charset=utf-8'})
      @req.body = jstring
      http_request
    end
  end

  # POST method to add netconfig element
  def create(json, e=@element)
    @element = e
    jstring = json
    unless json.is_a? String
      jstring = JSON.generate json
    end
    uri = URI.parse("https://#{@host}/")
    escaped = URI.escape("#{@docroot}#{e}")
    $DEBUG and puts "Post: #{escaped}"
    @http = Net::HTTP.new(uri.host, uri.port)
    @http.use_ssl = true
    @http.start do |http|
      @req = Net::HTTP::Post.new("#{escaped}",initheader = {"User-Agent" => @username, 'Content-Type' => 'application/json; charset=utf-8'})
      @req.body = jstring
      http_request
    end
  end

  # DELETE method
  def delete(e=@element)
    @element = e
    uri = URI.parse("https://#{@host}/")
    @http = Net::HTTP.new(uri.host, uri.port)
    @http.use_ssl = true
    escaped = URI.escape("#{@docroot}#{e}")
    $DEBUG and puts "Delete: #{escaped}"
    @http.start do |http|
      @req = Net::HTTP::Delete.new("#{escaped}",initheader = {"User-Agent" => @username })
      http_request
    end
  end

  def http_request
    @req.basic_auth(@username, @password)
    @response = @http.request(@req)
    if @response.code != "200"
      return "Error status code #{@response.code}\n#{@response.body}"
    end
    $DEBUG and puts "Response #{@response.code} #{@response.message}:#{@response.body}"
    return @response.body
  end

  # update config from file
  def restore(filepath=nil,clear_existing=false)
    unless filepath.nil?
      @config = JSON.parse(File.read(filepath))
    end
    @config.each do |c|
      case c.keys[0]
      when "hosts"
        $DEBUG and puts "RESTORE HOSTS"
        clear_existing and clear_hosts
        restore_hosts c["hosts"]
      when "routes"
        $DEBUG and puts "RESTORE ROUTES"
        clear_existing and clear_routes
        restore_routes c["routes"]
      when "interfaces"
        clear_existing and clear_vlans
        $DEBUG and puts "RESTORE INTERFACES"
        restore_interfaces c["interfaces"]
      end
    end
  end

  def clear_hosts
    get("hosts")["hosts"].each do |h|
      $DEBUG and puts "Clear host: #{h['name']}"
      delete "hosts/#{h['name']}"
    end
  end

  def restore_hosts(hosts)
    hosts.each do |h|
      $DEBUG and puts "create host: #{h['name']}"
      delete "hosts/#{h['name']}" # harmlessly fails when host does not exist
      create h,"hosts"
    end
  end

  def clear_interface(interface)
    json = { "v4_addrs"=>[], "v4_mask"=>"", "v4_dhcp"=>false,
      "v6_global_addrs"=>[], "v6_global_mask"=>""}
    modify json,"interfaces/#{interface}"
  end

  def clear_vlans
    get("interfaces")["interfaces"].each do |i|
      next if i['vlan'] == ""
      $DEBUG and puts "Clear vlan: #{i['name']}"
      delete "interfaces/#{i['name']}"
    end
  end

  def restore_interfaces(interfaces)
    interfaces.each do |i|
      next if i['name'].include? "eth" # don't do eth0 or eth1
      $DEBUG and puts "configure interface: #{i['name']}"
      unless i['vlan'] == ""
        interface,vlan = i['name'].split(".")
        create({"name"=>interface,"vlan"=>vlan},"interfaces")
      end
      modify i,"interfaces/#{i['name']}"
    end
  end

  def clear_routes
    get("routes")["routes"].each do |r|
      next if r['readonly'] == true
      $DEBUG and puts "Clear route: #{r['dst']}-#{r['gateway']}-#{r['interface_display_name'].downcase}"
      delete "routes/#{r['dst']}-#{r['gateway']}-#{r['interface_display_name'].downcase}"
    end
  end

  def restore_routes(routes)
    routes.each do |r|
      next if r['readonly'] == true
      $DEBUG and puts "configure route: #{r['dst']}-#{r['gateway']}-#{r['interface_display_name'].downcase}"
      create r,"routes"
    end
  end
  
  # write the json config to filepath
  def save(e="all", filepath="config.json")
    json = get(e)
    File.open(filepath,'w'){|f| f.write(JSON.pretty_generate(json))}
  end

  def usage
    puts "Usage: #{$0} <command> [-v(erbose) [json] [element]

ex: netconfig.rb modify '{\"v4_addr\":\"192.168.40.9\"}' \"hosts/dell-9\"
ex: netconfig.rb get hosts > myhosts.json
ex: netconfig.rb restore_hosts myhosts.json

commands:
   get [element]
   modify <json> <element>
   create <json> <element>
   delete <element>
   restore <config.json>
   clear_hosts
   restore_hosts <hosts.json>
   clear_interface <a1|a2|a3|a4|b1|b2|b3|b4>
   clear_vlans
   restore_interfaces <interfaces.json>
   clear_routes
   restore_routes <routes.json>
   save(e='all', filepath='config.json')
    "
  end
end

if $0 == __FILE__
  require 'pp'
  n = Netconfig.new
  unless ARGV[0]
    n.usage
    exit
  end
  $DEBUG=false
  if ARGV[0].include?("-v")
    $DEBUG=true
    ARGV.shift
  end

  config = Netconfig.new
  cmd = json = element = ''
  cmd = ARGV.shift
  case cmd
  when 'modify','create'
    json_or_filepath,element = ARGV
    json = json_or_filepath
    if File.readable? json_or_filepath
      json = File.read(json_or_filepath)
    end
    puts config.send(cmd.to_sym, JSON.parse(json), element)
  when 'get'
    config.get(ARGV.shift)
    puts config.response.body
  when 'delete','clear_interface'
    element = ARGV.shift
    puts config.send(cmd.to_sym, element)
  when 'clear_hosts','clear_vlans','clear_routes'
    config.send cmd.to_sym
  when /restore_(.+)/
    type = $1
    filepath = ARGV.shift
    json = JSON.parse(File.read(filepath))
    pp config.send(cmd.to_sym, json[type])
  when 'restore'
    filepath = ARGV.shift
    pp config.send(cmd.to_sym, filepath)
  when 'save'
    element = (ARGV[0].nil?) ? "all" : ARGV[0]
    filepath = (ARGV[1].nil?) ? "#{n.host}_config.json" : ARGV[1]
    puts config.save(element, filepath)
  else
    puts "Unsupported Command: #{cmd}"
    config.usage
  end

end